package com.dex4d.utils;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;

import com.dex4d.core.CustomApplication;
import com.dex4d.model.User;
import com.dex4d.model.WalletModel;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.crypto.MnemonicCode;
import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.wallet.DeterministicSeed;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.Wallet;
import org.web3j.crypto.WalletFile;
import org.web3j.protocol.ObjectMapperFactory;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

public class EthWalletUtil {


    public static final String TAG = EthWalletUtil.class.getSimpleName();

    public static SecureRandom secureRandom = new SecureRandom();
    private static final String ETH_TYPE = "m/44'/60'/0'/0/0";

    private static final String PATH = "M/44H/60H/0H/0/0";

    /**
     * 创建助记词语
     *
     * @return
     */
    public static List<String> getMnemonicCode() {
        Log.e("EthWalletUtil", "::" + System.currentTimeMillis());
        String password = "";
        long creationTimeSeconds = System.currentTimeMillis() / 1000;
        NetworkParameters params = TestNet3Params.get();
        DeterministicSeed seed = new DeterministicSeed(secureRandom, 128, password, creationTimeSeconds);
        List<String> mnemonic = seed.getMnemonicCode();
        return mnemonic;
    }


    /**
     * 创建钱包
     *
     * @param mnemonicCode
     * @param password 创建keystore的密码
     * @return
     */
    public static WalletModel createEthWallet(List<String> mnemonicCode, String password) {
        WalletModel walletModel = null;
        if (null != mnemonicCode && mnemonicCode.size() > 0) {
            walletModel = new WalletModel();
            String passphrase = "";
            long creationTimeSeconds = System.currentTimeMillis() / 1000;
            DeterministicSeed ds = new DeterministicSeed(mnemonicCode, null, passphrase, creationTimeSeconds);
            byte[] seedBytes = ds.getSeedBytes();

            if (seedBytes == null) {
                Log.e(TAG, "seedBytes is null");
                return null;
            }
            String[] pathArray = ETH_TYPE.split("/");
            DeterministicKey dkKey = HDKeyDerivation.createMasterPrivateKey(seedBytes);
            for (int i = 1; i < pathArray.length; i++) {
                ChildNumber childNumber;
                if (pathArray[i].endsWith("'")) {
                    int number = Integer.parseInt(pathArray[i].substring(0,
                            pathArray[i].length() - 1));
                    childNumber = new ChildNumber(number, true);
                } else {
                    int number = Integer.parseInt(pathArray[i]);
                    childNumber = new ChildNumber(number, false);
                }
                dkKey = HDKeyDerivation.deriveChildKey(dkKey, childNumber);
            }
            ECKeyPair keyPair = ECKeyPair.create(dkKey.getPrivKeyBytes());

            try {
                WalletFile walletFile = Wallet.createLight(password, keyPair);
                walletModel.address = "0x" + walletFile.getAddress();
                ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
                String jsonStr = objectMapper.writeValueAsString(walletFile);
                String pwd = password + getSignature().substring(0, 18);
                jsonStr = CryptoUtils.encrypt(jsonStr, pwd);
                walletModel.keystore = jsonStr;
                walletModel.mnemonicCodeList = mnemonicCode;
            } catch (Exception e) {
                Log.i(TAG, "createEthWallet error:" + e.getMessage());
                return null;
            }
            String name = "EthWallet";
            walletModel.name = name;
            walletModel.passphrase = password;
        }
        return walletModel;
    }


    public static String getPriKey(String keystore, String password) {
        String priKey = "";
        try {
            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
            WalletFile checkWalletFile = objectMapper.readValue(keystore, WalletFile.class);
            ECKeyPair ecKeyPair = Wallet.decrypt(password, checkWalletFile);
            ecKeyPair.getPrivateKey();
            priKey = "0x" + ecKeyPair.getPrivateKey().toString(16);
        } catch (Exception e) {
            android.util.Log.e(TAG, "getPriKey error:" + e.getMessage());
            priKey = "";
        }
        return priKey;
    }

    //用keystore还原钱包
    public static WalletModel restoreKeystore(String keystore, String password) {
        WalletModel walletModel = new WalletModel();
        try {
            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
            WalletFile checkWalletFile = objectMapper.readValue(keystore, WalletFile.class);

//            ECKeyPair ecKeyPair = Wallet.decrypt(password, checkWalletFile);
            ECKeyPair ecKeyPair = LWallet.decrypt(password, checkWalletFile);

            String randomPwd = BaseUtils.getRandomPwd();
            WalletFile walletFile = Wallet.createLight(randomPwd, ecKeyPair);
            String jsonStr = objectMapper.writeValueAsString(walletFile);  //用随机密码生成新的keystore

            String pwd = randomPwd + getSignature().substring(0, 18);
            jsonStr = CryptoUtils.encrypt(jsonStr, pwd);

            String name = "EthWallet";
            walletModel.address = "0x" + walletFile.getAddress();
            walletModel.keystore = jsonStr;
            walletModel.name = name;
            walletModel.passphrase = randomPwd+"";
        } catch (Exception e) {
            Log.i(TAG, "restore Keystore error:" + e.getMessage());
            return null;
        }
        return walletModel;
    }


    public static String exportKeystore(String password){
        String keystore = User.getInstance().getKeystore();
        String pwd = User.getInstance().getPassphrase() + getSignature().substring(0, 18);
        try {
            keystore = CryptoUtils.decrypt(keystore, pwd); //解密 得到原来的keystore

            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
            WalletFile checkWalletFile = objectMapper.readValue(keystore, WalletFile.class);
            ECKeyPair ecKeyPair = Wallet.decrypt(User.getInstance().getPassphrase(), checkWalletFile);

            WalletFile walletFile = Wallet.createLight(password, ecKeyPair);
            String jsonStr = objectMapper.writeValueAsString(walletFile); //得到新的keystore串
            return jsonStr;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    public static boolean checkPassowrd(String password) {
        String pubKey = "";
        try {
            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();

            String keystore = User.getInstance().getKeystore();
            String pwd = password + getSignature().substring(0, 18);
            keystore = CryptoUtils.decrypt(keystore, pwd);


            WalletFile checkWalletFile = objectMapper.readValue(keystore, WalletFile.class);
            ECKeyPair ecKeyPair = Wallet.decrypt(password, checkWalletFile);
            BigInteger pubKeyInteger = ecKeyPair.getPublicKey(); //如果能够解析出
            pubKey = "0x" + ecKeyPair.getPublicKey().toString(16);
            android.util.Log.i(TAG, "pubKey:" + pubKey);
        } catch (Exception e) {
            android.util.Log.e(TAG, "checkPassowrd error:" + e.getMessage());
            return false;
        }
        return true;
    }


    public static boolean checkMnemonic(List<String> list) {
        String passphrase = "";
        String password = "";
        long creationTimeSeconds = System.currentTimeMillis() / 1000;
        DeterministicSeed ds = new DeterministicSeed(list, null, passphrase, creationTimeSeconds);
        //根私钥
        byte[] seedBytes = ds.getSeedBytes();
        //助记词
        List<String> mnemonic = ds.getMnemonicCode();
        try {
            //助记词种子
            byte[] mnemonicSeedBytes = MnemonicCode.INSTANCE.toEntropy(mnemonic);
            ECKeyPair mnemonicKeyPair = ECKeyPair.create(mnemonicSeedBytes);
            WalletFile walletFile = Wallet.createLight(password, mnemonicKeyPair);
            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
            //存这个keystore 用完后删除
            String jsonStr = objectMapper.writeValueAsString(walletFile);
            android.util.Log.i(TAG, "keystore:" + jsonStr);
            //验证
            WalletFile checkWalletFile = objectMapper.readValue(jsonStr, WalletFile.class);
            ECKeyPair ecKeyPair = Wallet.decrypt(password, checkWalletFile);
            byte[] checkMnemonicSeedBytes = Numeric.hexStringToByteArray(ecKeyPair.getPrivateKey().toString(16));
            List<String> checkMnemonic = MnemonicCode.INSTANCE.toMnemonic(checkMnemonicSeedBytes);
        } catch (MnemonicException.MnemonicLengthException | MnemonicException.MnemonicWordException | MnemonicException.MnemonicChecksumException | CipherException | IOException e) {
            android.util.Log.e(TAG, "checkMnemonic error:" + e.getMessage());
            return false;
        }
        return true;
    }

    public static String getSignature() {
        Context context = CustomApplication.getInstance();
        PackageManager pm = context.getPackageManager();
        PackageInfo pi;
        StringBuilder sb = new StringBuilder();
        try {
            pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
            Signature[] signatures = pi.signatures;
            for (Signature signature : signatures) {
                sb.append(signature.toCharsString());
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        String md5 = CryptoUtils.MD5(sb.toString());
        return md5;
    }

    public static String getTransactionSigned(String toAddr, int nonceId, String value, String gasPrice, String password) {
        BigInteger GAS_PRICE =  new BigInteger(gasPrice);
        android.util.Log.i("signed", "getTransactionSigned gas price:" + GAS_PRICE.toString());
        BigInteger GAS_LIMIT = BigInteger.valueOf(21000);
        String toAddress = toAddr;
        String keystore = User.getInstance().getKeystore();

        String pwd = password + getSignature().substring(0, 18);
        try {
            keystore = CryptoUtils.decrypt(keystore, pwd); //解密
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }

        String priKey = EthWalletUtil.getPriKey(keystore, password);
        Credentials credentials = Credentials.create(priKey);
        String nonce = nonceId + "";
        BigInteger intNonce = new BigInteger(nonce);
        BigInteger intValue = Convert.toWei(value, Convert.Unit.ETHER).toBigInteger();
        RawTransaction rawTransaction = RawTransaction.createTransaction(
                intNonce, GAS_PRICE, GAS_LIMIT, toAddress, intValue, "");
        //签名Transaction，这里要对交易做签名
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        String hexValue = Numeric.toHexString(signedMessage);
        return hexValue;
    }

    public static String getTokenTransactionSigned(String contractAddress, String toAddress, int nonceId, String value, String gasPrice,  String password) {
        BigInteger GAS_PRICE = new BigInteger(gasPrice);
        BigInteger GAS_LIMIT = BigInteger.valueOf(144000);

        String keystore = User.getInstance().getKeystore();
        String pwd = password + getSignature().substring(0, 18);
        try {
            keystore = CryptoUtils.decrypt(keystore, pwd); //解密
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }

        String priKey = EthWalletUtil.getPriKey(keystore, password);
        Credentials credentials = Credentials.create(priKey);
        String nonce = nonceId + "";
        BigInteger intNonce = new BigInteger(nonce);
        BigInteger intValue = Convert.toWei(value, Convert.Unit.ETHER).toBigInteger();
        String data = getSmartContractCode(toAddress, intValue);

        RawTransaction rawTransaction = RawTransaction.createTransaction(intNonce, GAS_PRICE, GAS_LIMIT, contractAddress, BigInteger.ZERO, data);
        //签名Transaction，这里要对交易做签名
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        String hexValue = Numeric.toHexString(signedMessage);
        return hexValue;
    }

    public static String getSmartContractCode(String toAddress, BigInteger intValue) {
        String methodName = "transfer"; // 函数名是转账
        List<Type> inputParameters = new ArrayList<>();
        List<TypeReference<?>> outputParameters = new ArrayList<>();
        Address tAddress = new Address(toAddress);

        Uint256 value = new Uint256(intValue);
        inputParameters.add(tAddress);
        inputParameters.add(value);

        TypeReference<Bool> typeReference = new TypeReference<Bool>() {
        };
        outputParameters.add(typeReference);

        Function function = new Function(methodName, inputParameters, outputParameters);
        String data = FunctionEncoder.encode(function);
        return data;
    }
}
